package iceberg.readers;

import iceberg.constraints.ConstraintException;
import iceberg.constraints.ConstraintExceptionImpl;
import iceberg.domain.ArchitectureDefinition;
import iceberg.domain.Functionality;
import iceberg.domain.Layer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Reads the architecture definition. An example definition:
 * <code>
 * <architectureDefinition>
 * <layer name="View">
 * <allowedToAccess>
 * <accessLayer name="Control"/>
 * </allowedToAccess>
 * </layer>
 * <p/>
 * <layer name="Control">
 * <allowedToAccess>
 * <accessLayer name="Model"/>
 * </allowedToAccess>
 * </layer>
 * <p/>
 * <layer name="Model">
 * <allowedToAccess></allowedToAccess>
 * </layer>
 * </architectureDefinition>
 * </code>
 */
class ArchitectureDefinitionReader extends AbstractReader {

	private static final Logger LOG = LoggerFactory.getLogger(ArchitectureDefinitionReader.class);
	private final Map<String, Layer> layers = new HashMap<String, Layer>();
	private final ArchitectureDefinition definition = new ArchitectureDefinition();

	public ArchitectureDefinition read() throws SAXException, IOException, ParserConfigurationException {
		Element root = getXMLRootOfFile("AD.XML");

		NodeList layerList = root.getElementsByTagName("layer");
		LOG.info("Number of layers {}", layerList.getLength());

		createLayers(layerList);
		buildLayers(layerList);

		Map<String, ConstraintException> constraintExceptions = buildExceptions(root);

		definition.setLookupMap(layers);
		definition.setConstraintExceptionMap(constraintExceptions);

		return definition;

	}

	private Map<String, ConstraintException> buildExceptions(Element root) {
		NodeList exceptionList = root.getElementsByTagName("exception");
		return buildConstraintExceptions(exceptionList);
	}

	private void buildLayers(NodeList layerList) {
		for (int i = 0; i < layerList.getLength(); i++) {
			Element layerElement = (Element) layerList.item(i);
			String layerName = getLayerName(layerElement);

			Layer layer = getLayer(layerName);
			buildLayer((Element) layerList.item(i), layer);
		}
	}

	private void createLayers(NodeList layerList) {
		for (int i = 0; i < layerList.getLength(); i++) {
			Element layerElement = (Element) layerList.item(i);
			String layerName = getLayerName(layerElement);
			int layerNumber = Integer.parseInt(getLayerNumber(layerElement));
			Functionality functionality = getFunctionality(layerElement);

			addLayer(layerName, layerNumber, functionality);
		}
	}

	private Functionality getFunctionality(Element layerElement) {
		String func = layerElement.getAttribute("function");
		if (func.equals("")) {
			return Functionality.UNDEFINED;
		} else if (func.equals("gui")) {
			return Functionality.GUI;
		} else if (func.equals("domain")) {
			return Functionality.DOMAIN;
		}

		throw new IcebergException(	"Unknown functionality");
	}

	private Map<String, ConstraintException> buildConstraintExceptions(NodeList exceptionList) {
		Map<String, ConstraintException> exceptionMap = new HashMap<String, ConstraintException>();

		for (int i = 0; i < exceptionList.getLength(); i++) {
			Element element = (Element) exceptionList.item(i);
			String name = element.getAttribute("name");
			String argument = getAttributeOfFirstElement(element, "reasoning", "argument");
			String rule = getAttributeOfFirstElement(element, "rule", "name");

			ConstraintException constraintException = new ConstraintExceptionImpl(name, argument, rule);
			LOG.info("Found constraint exception " + constraintException);
			exceptionMap.put(name, constraintException);
		}

		return exceptionMap;
	}

	private String getAttributeOfFirstElement(Element element, String elementName, String attributeName) {
		return ((Element) element.getElementsByTagName(elementName).item(0)).getAttribute(attributeName);
	}

	private void addLayer(String layerName, int layerNumber, Functionality functionality) {

		Layer layer;
		if (functionality == null) {
			layer = new Layer(layerName, layerNumber);
		} else {
			layer = new Layer(layerName, layerNumber, functionality);
		}
		layers.put(layerName, layer);
		LOG.info("Create layer '{}'", layerName);
	}

	private Layer getLayer(String layerName) {
		return layers.get(layerName);
	}

	private void buildLayer(Element item, Layer layer) {
		Element allowedToAccessElement = (Element) item.getElementsByTagName("allowedToAccess").item(0);

		if (allowedToAccessElement != null) {
			NodeList accessElements = allowedToAccessElement.getElementsByTagName("accessLayer");

			for (int i = 0; i < accessElements.getLength(); i++) {
				Element layerElement = (Element) accessElements.item(i);
				String layerName = getLayerName(layerElement);
				layer.addAllowedToAccessLayer(getLayer(layerName));
				LOG.info("Added '{}' to '{}'", layerName, layer.getName());

			}
		} else {
			LOG.error("Unable to locate allowedToAccess element for layer '{}'", layer);
		}
	}

	private String getLayerName(Element layer) {
		return layer.getAttribute("name");
	}

	private String getLayerNumber(Element layer) {
		return layer.getAttribute("number");
	}

}

